package me.ydq.core;

import lombok.AccessLevel;
import lombok.experimental.FieldDefaults;
import me.ydq.convert.ColumnConvert;
import me.ydq.core.sql.Sql;
import me.ydq.core.table.TableManual;
import me.ydq.dialect.Dialect;
import me.ydq.misc.Fn;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.dao.IncorrectResultSizeDataAccessException;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Collectors;

import static me.ydq.core.sql.Sql.*;


@FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE)
final class JdbcTable<T> implements Table<T> {

    NamedParameterJdbcTemplate jdbcTemplate;

    Class<T>       cls;
    String         table;
    Dialect        dialect;
    JdbcSqlSpec<T> where;
    JdbcSqlSpec<T> having;
    RowMapper<T>   mapper;
    List<String>   columns = new ArrayList<>();
    List<String>   orderBy = new ArrayList<>();
    List<String>   groupBy = new ArrayList<>();
    ColumnConvert  columnConvert;

    JdbcTable(NamedParameterJdbcTemplate jdbcTemplate,
              Dialect dialect,
              Class<T> cls,
              String table,
              ColumnConvert columnConvert,
              RowMapper<T> rowMapper) {
        this.cls = cls;
        this.jdbcTemplate = jdbcTemplate;
        this.dialect = dialect;
        this.where = new JdbcSqlSpec<>(cls);
        this.having = new JdbcSqlSpec<>(where);
        this.table = table;
        this.columnConvert = columnConvert;
        this.mapper = rowMapper;
    }

    @Override
    public Class<T> cls() {
        return cls;
    }

    @Override
    public RowMapper<T> rowMapper() {
        return mapper;
    }

    @Override
    public String table() {
        return table;
    }

    @Override
    public NamedParameterJdbcTemplate jdbcTemplate() {
        return jdbcTemplate;
    }

    @Override
    public Map<String, Object> params() {
        return where.getParams();
    }


    public Table<T> select(String rawColumns) {
        columns.add(rawColumns);
        return this;
    }

    public Table<T> select(List<Fn<T, ?>> selectColumns) {
        for (Fn<T, ?> column : selectColumns) {
            columns.add(dialect.wordWrap(columnConvert.convert(cls, column)));
        }
        return this;
    }

    public Table<T> where(Consumer<SqlSpec<T>> sql) {
        sql.accept(this.where);
        return this;
    }

    public Table<T> having(Consumer<SqlSpec<T>> sql) {
        sql.accept(this.having);
        return this;
    }

    public Table<T> asc(List<Fn<T, ?>> columns) {
        for (Fn<T, ?> column : columns) {
            orderBy.add(dialect.wordWrap(columnConvert.convert(cls, column)) + ASC);
        }
        return this;
    }

    public Table<T> desc(List<Fn<T, ?>> columns) {
        for (Fn<T, ?> column : columns) {
            orderBy.add(dialect.wordWrap(columnConvert.convert(cls, column)) + DESC);
        }
        return this;
    }

    public Table<T> asc(String... columns) {
        for (String column : columns) {
            orderBy.add(dialect.wordWrap(column) + ASC);
        }
        return this;
    }

    public Table<T> desc(String... columns) {
        for (String column : columns) {
            orderBy.add(dialect.wordWrap(column) + DESC);
        }
        return this;
    }

    public Table<T> order(String orderByRaw) {
        orderBy.add(orderByRaw);
        return this;
    }

    public Table<T> groupBy(List<Fn<T, ?>> columns) {
        for (Fn<T, ?> column : columns) {
            groupBy.add(dialect.wordWrap(columnConvert.convert(cls, column)));
        }
        return this;
    }

    public Table<T> groupBy(String groupByRaw) {
        groupBy.add(groupByRaw);
        return this;
    }

    public <R> List<R> list(RowMapper<R> mapper) {
        return jdbcTemplate.query(
                select() + table + where() + groupBy() + having() + orderBy(),
                where.getParams(),
                mapper
        );
    }

    public <R> List<R> list(int offset, int size, RowMapper<R> mapper) {
        return jdbcTemplate.query(
                dialect.toPage(select() + table + where() + groupBy() + having() + orderBy(), offset, size),
                where.getParams(),
                mapper
        );
    }

    @Override
    public <R> R unique(Map<Fn<T, ?>, Object> equalConditions, RowMapper<R> rowMapper) {
        if (equalConditions != null && !equalConditions.isEmpty()) {
            equalConditions.forEach(where::eq);
        }
        var unique = list(0, 2, rowMapper);
        if (unique.isEmpty()) {
            throw new EmptyResultDataAccessException(1);
        }
        if (unique.size() > 1) {
            throw new IncorrectResultSizeDataAccessException("Incorrect result size: expected 1, actual greater than 1", 1);
        }
        return unique.get(0);
    }

    /**
     * 根据 column 自动适配 count语句
     */
    public Integer count() {
        String rawColumn = columns();
        if (!groupBy.isEmpty() || !having.criteria.isEmpty()) {
            //如果有聚合函数 或者 having 条件 则采用子查询的形式查询 count
            return jdbcTemplate.queryForObject(
                    SELECT_COUNT("*") + "(" + SELECT(rawColumn) + table + where() + groupBy() + having() + ") AS sub_query",
                    where.getParams(),
                    Integer.class
            );
        }

        boolean isMultiColumn = rawColumn.contains(COMMA);
        if (!isMultiColumn && rawColumn.length() > 1 && rawColumn.toUpperCase().contains(" AS ")) {
            rawColumn = removeAliases(rawColumn);
        }

        //否则 如果是多字段（包含逗号） 则 count(*) 如果是单字段，不包含逗号 则 count 该字段
        return jdbcTemplate.queryForObject(
                SELECT_COUNT(isMultiColumn ? "*" : rawColumn) + table + where(),
                where.getParams(),
                Integer.class
        );
    }

    @Override
    public int delete() {
        return jdbcTemplate.update(
                DELETE + table + where(),
                where.getParams()
        );
    }

    @Override
    public int update(Map<Fn<T, ?>, ?> updates) {
        String sets = where.updateSets(columnConvert, updates);
        return jdbcTemplate.update(
                UPDATE(table, sets, where()),
                where.getParams()
        );
    }

    @Override
    public int updateByScript(String rawSetSQLScript) {
        return jdbcTemplate.update(
                UPDATE(table, rawSetSQLScript, where()),
                where.getParams()
        );
    }

    @Override
    public String select() {
        return SELECT(columns());
    }

    @Override
    public String where() {
        return conditions(where, WHERE);
    }

    @Override
    public String having() {
        return conditions(having, HAVING);
    }

    @Override
    public String columns() {
        if (columns.isEmpty()) {
            return "*";
        }
        return String.join(COMMA, columns);
    }

    @Override
    public String orderBy() {
        return ORDER_BY(orderBy);
    }

    @Override
    public String groupBy() {
        return GROUP_BY(groupBy);
    }

    String conditions(JdbcSqlSpec<T> sql, String prefix) {
        String conditions = sql.getCriteria()
                .stream()
                .map(c -> c.toSql(dialect, columnConvert))
                .filter(Sql::HAS_CONDITION)
                .collect(Collectors.joining(AND, prefix, EMPTY));
        return HAS_CONDITION(conditions) ? conditions : EMPTY;
    }


    @Override
    public void useTable(Consumer<TableManual<T>> consumer) {
        consumer.accept(this);
    }

    @Override
    public <R> R useTable(Function<TableManual<T>, R> func) {
        return func.apply(this);
    }

    @Override
    public Table<T> reset() {
        where.criteria.clear();
        having.criteria.clear();
        where.params.clear();
        where.index.set(0);
        columns.clear();
        orderBy.clear();
        groupBy.clear();
        return this;
    }
}
